package monitor

import (
	"fmt"
	"gitee.com/aldrice/monitor/api/common"
	"gitee.com/aldrice/monitor/ent"
	"gitee.com/aldrice/monitor/ent/alarmlog"
	"gitee.com/aldrice/monitor/ent/metric"
	"gitee.com/aldrice/monitor/ent/object"
	"gitee.com/aldrice/monitor/ent/rule"
	"gitee.com/aldrice/monitor/ent/user"
	"gitee.com/aldrice/monitor/internal/router"
	"strconv"
	"time"
)

type createRuleReq struct {
	Nickname   string  `json:"nickname" validate:"required"`
	MaxAlarm   float64 `json:"max_alarm"`
	MinAlarm   float64 `json:"min_alarm"`
	AlarmLevel int     `json:"alarm_level" validate:"required"`

	ObjectID int    `json:"object_id" validate:"required"`
	Metric   int    `json:"metric" validate:"required"`
	IsEnable string `json:"is_enable" validate:"required"`
}

var (
	errInvalidAlarmRange = router.ApiError(12005, "invalid alarm range")
)

// CreateRule
// @Summary Rule Create
// @Description 用户针对某台名下的机器创建告警规则
// @ID monitor-rule-create
// @Tags rule
// @Accept json
// @Produce json
// @Router /monitor/rule [post]
func CreateRule(c *router.Context) error {
	req := new(createRuleReq)
	if err := c.Bind(req); err != nil {
		return err
	}
	if err := c.Validate(req); err != nil {
		return err
	}

	ctx, db, uid := c.Ctx(), c.DB(), c.UID()

	// 首先查询req中所指明的object是否属于该用户
	_, err := db.Object.Query().Where(
		object.IDEQ(req.ObjectID),
		object.HasOwnedByWith(user.IDEQ(uid)),
	).First(ctx)
	if err != nil {
		if ent.IsNotFound(err) {
			return errInvalidObjectID
		}
		return err
	}

	// 检查最小值和最大值
	if !verifyRange(req.MinAlarm, req.MaxAlarm) {
		return errInvalidAlarmRange
	}

	// 创建该规则
	rCrt := db.Rule.Create().
		SetNickname(req.Nickname).
		SetAlarmLevel(req.AlarmLevel).
		SetToObjectID(req.ObjectID).
		SetInMetricID(req.Metric).
		SetEnable(req.IsEnable == "true")

	// 当警报值大于等于0时，说明其存在
	if req.MaxAlarm >= 0 {
		rCrt.SetMaxAlarm(req.MaxAlarm)
	}
	if req.MinAlarm >= 0 {
		rCrt.SetMinAlarm(req.MinAlarm)
	}

	r, err := rCrt.Save(ctx)
	if err != nil {
		return err
	}

	return c.ApiSuccess(r.ID)
}

type editRuleReq struct {
	Nickname   string  `json:"nickname" validate:"required"`
	MaxAlarm   float64 `json:"max_alarm" validate:"required"`
	MinAlarm   float64 `json:"min_alarm" validate:"required"`
	AlarmLevel int     `json:"alarm_level" validate:"required"`
	IsEnable   string  `json:"is_enable" validate:"required"`
	Metric     string  `json:"metric" validate:"required"`

	ID int `json:"id" validate:"required"`
}

var (
	errInvalidRuleID = router.ApiError(12006, "invalid rule id")
)

// EditRule
// @Summary Edit Rule
// @Description 用户修改告警规则
// @ID monitor-rule-edit
// @Tags rule
// @Accept json
// @Produce json
// @Router /monitor/rule [put]
func EditRule(c *router.Context) error {
	req := new(editRuleReq)
	if err := c.Bind(req); err != nil {
		return err
	}
	if err := c.Validate(req); err != nil {
		return err
	}

	ctx, db, uid := c.Ctx(), c.DB(), c.UID()

	// 首先查询req中所指明的object是否属于该用户
	r, err := db.Rule.Query().Where(
		rule.IDEQ(req.ID),
		rule.HasToObjectWith(object.HasOwnedByWith(user.IDEQ(uid))),
	).First(ctx)
	if err != nil {
		if ent.IsNotFound(err) {
			return errInvalidRuleID
		}
		return err
	}

	mid, err := db.Metric.Query().Where(metric.NameEqualFold(req.Metric)).FirstID(ctx)
	if err != nil {
		return err
	}

	// 检查最小值和最大值
	if !verifyRange(req.MinAlarm, req.MaxAlarm) {
		return errInvalidAlarmRange
	}

	// 更新告警阈值
	rUpt := r.Update().
		SetNickname(req.Nickname).
		SetAlarmLevel(req.AlarmLevel).
		SetEnable(req.IsEnable == "true").
		SetInMetricID(mid)
	if req.MaxAlarm < 0 {
		rUpt.ClearMaxAlarm()
	} else {
		rUpt.SetMaxAlarm(req.MaxAlarm)
	}
	if req.MinAlarm < 0 {
		rUpt.ClearMinAlarm()
	} else {
		rUpt.SetMinAlarm(req.MinAlarm)
	}

	_, err = rUpt.Save(ctx)
	if err != nil {
		return err
	}
	return c.ApiSuccess(nil)
}

func verifyRange(min, max float64) bool {
	// 当最小值和最大值都不存在时报错
	if min == -1 && max == -1 {
		return false
	}
	// 当最小值和最大值都存在，且最小值大于等于最大值时报错
	if min >= max && min > 0 && max > 0 {
		return false
	}
	return true
}

type getRulesRespItem struct {
	ent.Rule
	Metric   string `json:"metric"`
	Range    string `json:"range"`
	IsEnable string `json:"is_enable"`
}

// GetRules
// @Summary Get Rules
// @Description 用户获取所属机器下的所有符合条件的规则
// @ID monitor-rule-gets
// @Tags rule
// @Accept json
// @Produce json
// @Route /monitor/rules [get]
func GetRules(c *router.Context) error {
	db, ctx := c.DB(), c.Ctx()

	rQry := db.Rule.Query()

	if c.QueryParam("object_id") != "" {
		oid, err := strconv.Atoi(c.QueryParam("object_id"))
		if err != nil {
			return err
		}
		_, err = db.User.Query().Where(user.IDEQ(c.UID()), user.HasHaveObjectWith(object.IDEQ(oid))).First(ctx)
		if err != nil {
			return err
		}

		rQry.Where(rule.HasToObjectWith(object.IDEQ(oid))).WithInMetric()
	}

	cnt, err := rQry.Clone().Count(ctx)
	if err != nil {
		return err
	}
	if cnt == 0 {
		return c.ApiTable(0, nil)
	}

	page, limit := c.DeterminePage()
	ord := c.DetermineOrder(rule.Columns)
	rules, err := rQry.Limit(limit).Offset(page).Order(ord).All(ctx)
	if err != nil {
		return err
	}

	items := make([]getRulesRespItem, len(rules))
	for i, r := range rules {
		min, max := "-inf", "+inf"
		if r.MinAlarm != nil {
			min = strconv.FormatFloat(*r.MinAlarm, 'f', 2, 64)
		}
		if r.MaxAlarm != nil {
			max = strconv.FormatFloat(*r.MaxAlarm, 'f', 2, 64)
		}
		items[i] = getRulesRespItem{
			Rule:     *r,
			Range:    fmt.Sprintf("[ %s , %s ]", min, max),
			Metric:   fmt.Sprintf("%s (ID: %d)", r.Edges.InMetric.Name, r.Edges.InMetric.ID),
			IsEnable: strconv.FormatBool(r.Enable),
		}
	}

	return c.StdTable(cnt, items)
}

// DeleteRule
// @Summary Delete Rules
// @Description 删除规则
// @ID monitor-rule-delete
// @Tags rule
// @Accept json
// @Produce json
// @Route /monitor/rule [delete]
func DeleteRule(c *router.Context) error {
	rID, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		return err
	}

	_, err = c.DB().Rule.Delete().Where(
		rule.IDEQ(rID),
		rule.HasToObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))),
	).Exec(c.Ctx())
	if err != nil {
		return err
	}

	return c.ApiSuccess(nil)
}

// GetRule
// @Summary Get Rule
// @Description 删除规则
// @ID monitor-rule-get
// @Tags rule
// @Accept json
// @Produce json
// @Route /monitor/rule [get]
func GetRule(c *router.Context) error {
	rID, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		return err
	}

	r, err := c.DB().Rule.Query().Where(
		rule.IDEQ(rID),
		rule.HasToObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))),
	).WithInMetric().First(c.Ctx())
	if err != nil {
		return err
	}
	min, max := "-inf", "+inf"
	if r.MinAlarm != nil {
		min = strconv.FormatFloat(*r.MinAlarm, 'f', 2, 64)
	}
	if r.MaxAlarm != nil {
		max = strconv.FormatFloat(*r.MaxAlarm, 'f', 2, 64)
	}
	return c.ApiSuccess(getRulesRespItem{
		Rule:     *r,
		Metric:   r.Edges.InMetric.Name,
		Range:    fmt.Sprintf("[ %s , %s ]", min, max),
		IsEnable: strconv.FormatBool(r.Enable),
	})
}

type alarmLevel struct {
	Value int `json:"value"`
}

// GetAlarmLevel
// @Summary Get Alarm Level
// @Description 获取支持的告警级别
// @ID monitor-alarm-level
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_level [get]
func GetAlarmLevel(c *router.Context) error {
	return c.ApiSuccess([]alarmLevel{{Value: 1}, {Value: 2}, {Value: 3}})
}

// GetStatus
// @Summary Get Rule Status
// @Description 获取当前信息概览
// @ID monitor-rule-status
// @Tags rule
// @Produce json
// @Route /monitor/rule/status [get]
func GetStatus(c *router.Context) error {
	ctx := c.Ctx()
	alQry := c.DB().AlarmLog.Query().Where(alarmlog.HasSentForObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))))

	if c.QueryParam("length") != "" {
		// 获取时间长度参数, 单位： 小时
		tl, err := strconv.Atoi(c.QueryParam("length"))
		if err != nil {
			return err
		}
		alQry.Where(alarmlog.AlarmAtGTE(time.Now().Add(-time.Duration(tl) * time.Hour)))
	}
	if c.QueryParam("level") != "" {
		// 获取告警级别参数
		lv, err := strconv.Atoi(c.QueryParam("level"))
		if err != nil {
			return err
		}
		if lv != 4 {
			alQry.Where(alarmlog.RecordLevelEQ(lv))
		}
	}

	cnt, err := alQry.Count(ctx)
	if err != nil {
		return err
	}
	return c.ApiSuccess(cnt)
}

type chartRep struct {
	AxisX []string `json:"axis_x"`
	AxisY []int    `json:"axis_y"`
	Count int      `json:"count"`
}

// GetAlarmLogsChart
// @Summary Get Alarm Logs Chart
// @Description 获取告警记录
// @ID monitor-rule-logs-chart
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_logs [get]
func GetAlarmLogsChart(c *router.Context) error {
	ctx := c.Ctx()

	alQry := c.DB().AlarmLog.Query().Where(alarmlog.HasSentForObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))))
	if c.QueryParam("object_id") != "" {
		oid, err := strconv.Atoi(c.QueryParam("object_id"))
		if err != nil {
			return err
		}
		alQry.Where(alarmlog.HasSentForObjectWith(object.IDEQ(oid)))
	}

	// 获取时间长度参数, 单位： 小时
	tl, err := strconv.Atoi(c.QueryParam("length"))
	if err != nil {
		return err
	}
	als, err := alQry.Where(alarmlog.AlarmAtGTE(time.Now().Add(-time.Duration(tl) * time.Hour))).All(ctx)
	if err != nil {
		return err
	}
	asx := make([]string, len(als))
	asy := make([]int, len(als))
	for i, al := range als {
		asx[i] = al.AlarmAt.Format("02-15:04:05")
		asy[i] = al.RecordLevel
	}
	return c.ApiSuccess(chartRep{
		AxisX: asx,
		AxisY: asy,
	})
}

// GetAlarmCount
// @Summary Get Alarm Logs Count
// @Description 获取告警记录数量
// @ID monitor-rule-logs-count
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_logs/count [get]
func GetAlarmCount(c *router.Context) error {
	ctx := c.Ctx()
	alQry := c.DB().AlarmLog.Query().Where(alarmlog.HasSentForObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))))

	if c.QueryParam("object_id") != "" {
		oid, err := strconv.Atoi(c.QueryParam("object_id"))
		if err != nil {
			return err
		}
		alQry.Where(alarmlog.HasSentForObjectWith(object.IDEQ(oid)))
	}

	// 获取时间长度参数, 单位： 小时
	tl, err := strconv.Atoi(c.QueryParam("length"))
	if err != nil {
		return err
	}
	als, err := alQry.Where(alarmlog.AlarmAtGTE(time.Now().Add(-time.Duration(tl) * time.Hour))).All(ctx)
	if err != nil {
		return err
	}

	asy := []int{0, 0, 0}
	for _, al := range als {
		asy[al.RecordLevel-1]++
	}

	if c.QueryParam("is_pie") == "true" {
		a := []sourceItem{
			{
				Name:  "一级告警",
				Value: asy[0],
			},
			{
				Name:  "二级告警",
				Value: asy[1],
			},
			{
				Name:  "三级告警",
				Value: asy[2],
			},
		}
		return c.ApiSuccess(a)
	}

	return c.ApiSuccess(chartRep{
		AxisX: []string{"一级告警", "二级告警", "三级告警"},
		AxisY: asy,
		Count: len(als),
	})
}

type sourceItem struct {
	Name  string `json:"name"`
	Value int    `json:"value"`
}

// GetAlarmSource
// @Summary Get Alarm Logs Source
// @Description 获取告警记录数量
// @ID monitor-rule-logs-source
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_logs/source [get]
func GetAlarmSource(c *router.Context) error {
	ctx, db := c.Ctx(), c.DB()

	objs, err := db.Object.Query().Where(object.HasOwnedByWith(user.IDEQ(c.UID()))).All(ctx)
	if err != nil {
		return err
	}

	// 获取时间长度参数, 单位： 小时
	tl, err := strconv.Atoi(c.QueryParam("length"))
	if err != nil {
		return err
	}

	var item []sourceItem
	for _, obj := range objs {
		cnt, err := obj.QueryHasLogs().Where(alarmlog.AlarmAtGTE(time.Now().Add(-time.Duration(tl) * time.Hour))).Count(ctx)
		if err != nil {
			return err
		}
		if cnt == 0 {
			continue
		}
		item = append(item, sourceItem{
			Name:  obj.Nickname,
			Value: cnt,
		})
		if len(item) == 4 {
			break
		}
	}
	return c.ApiSuccess(item)
}

type alarmRadarRsp struct {
	Metric []string `json:"metric"`
	Level1 []int    `json:"level_1"`
	Level2 []int    `json:"level_2"`
	Level3 []int    `json:"level_3"`
	Max    int      `json:"max"`
}

// GetAlarmRadar
// @Summary Get Alarm Logs Radar
// @Description 获取告警记录雷达图
// @ID monitor-rule-logs-radar
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_logs/radar [get]
func GetAlarmRadar(c *router.Context) error {
	ctx, db := c.Ctx(), c.DB()

	ms, err := db.Metric.Query().All(ctx)
	if err != nil {
		return err
	}

	// 获取时间长度参数, 单位： 小时
	tl, err := strconv.Atoi(c.QueryParam("length"))
	if err != nil {
		return err
	}

	metrics := make([]string, len(ms))
	level1 := make([]int, len(ms))
	level2 := make([]int, len(ms))
	level3 := make([]int, len(ms))
	max := -1

	for i, m := range ms {
		metrics[i] = m.Name
		logs, err := m.QueryHasLogs().Where(
			alarmlog.AlarmAtGTE(time.Now().Add(-time.Duration(tl)*time.Hour)),
			alarmlog.HasSentForObjectWith(object.HasOwnedByWith(user.IDEQ(c.UID()))),
		).All(ctx)
		if err != nil {
			return err
		}
		for _, l := range logs {
			switch l.RecordLevel {
			case 1:
				level1[i]++
				max = common.Compare(max, level1[i])
			case 2:
				level2[i]++
				max = common.Compare(max, level2[i])
			case 3:
				level3[i]++
				max = common.Compare(max, level3[i])
			default:
			}
		}
	}

	return c.ApiSuccess(alarmRadarRsp{
		Metric: metrics,
		Level1: level1,
		Level2: level2,
		Level3: level3,
		Max:    max,
	})
}

// GetAlarmLogs
// @Summary Get Alarm Logs
// @Description 获取告警记录列表
// @ID monitor-rule-logs
// @Tags rule
// @Produce json
// @Route /monitor/rule/alarm_logs [get]
func GetAlarmLogs(c *router.Context) error {
	oid, err := strconv.Atoi(c.QueryParam("object_id"))
	if err != nil {
		return err
	}

	qry := c.DB().AlarmLog.Query().Where(alarmlog.HasSentForObjectWith(
		object.HasOwnedByWith(user.IDEQ(c.UID())),
		object.IDEQ(oid),
	))

	if c.QueryParam("detail") != "" {
		qry.Where(alarmlog.DetailContainsFold(c.QueryParam("detail")))
	}

	ctx := c.Ctx()
	cnt, err := qry.Clone().Count(ctx)
	if err != nil {
		return err
	}
	if cnt == 0 {
		return c.ApiTable(0, nil)
	}

	limit, offset := c.DeterminePage()
	ord := c.DetermineOrder(alarmlog.Columns)
	item, err := qry.Limit(limit).Offset(offset).Order(ord).All(ctx)
	if err != nil {
		return err
	}
	return c.ApiTable(cnt, item)
}
